use std::fmt::Debug;
use std::net::IpAddr;
use std::ops::Deref;
use std::path::Prefix::Disk;
use std::string::ToString;
use std::sync::OnceLock;

use local_ip_address::local_ip;
use log::error;
use mac_address::get_mac_address;
use rand_distr::num_traits::ops::bytes;
use rand_distr::num_traits::real::Real;
use serde_json::value::Index;
use sysinfo::{Disks, Groups, MacAddr, Networks, Process, ProcessRefreshKind, System, UpdateKind, Users};

use crate::dto::sys_dto::{CpuDto, DiskDto, GroupDto, HostDto, MemoryDto, NetDto, ProcessDto, UserDto};

static mut HOST_INFO_G: OnceLock<HostInfoInner> = OnceLock::new();
#[cfg(target_os = "windows")]
const OS_TYPE: &str = "windows";
#[cfg(target_os = "linux")]
const OS_TYPE: &str = "linux";

/// 字节计算单位大小
enum SizeUnit {
    G,
    M,
    K,
}

struct HostInfoInner {
    sys: System,
    pub mac: String,
    pub ip: String,
    pub users: Users,
}

pub fn init() {
    let hi = HostInfoInner::new();
    unsafe { HOST_INFO_G.get_or_init(|| hi); }
}

impl HostInfoInner {
    pub fn new() -> HostInfoInner {
        let mut sys: System = System::new();
        sys.refresh_all();
        return HostInfoInner {
            sys,
            users: Users::new_with_refreshed_list(),
            ip: local_ip().and_then(|addr| Ok(addr.to_string())).unwrap_or("".to_string()),
            mac: match get_mac_address() {
                Ok(Some(ma)) => {
                    let macc = format!("{}", ma);
                    macc
                }
                Ok(None) => String::from("no"),
                Err(e) => {
                    error!("获取mac地址失败->{:?}", e);
                    String::new()
                }
            },
        };
    }
}


pub fn get_ip() -> String {
    unsafe { HOST_INFO_G.get().and_then(|hi| Some(hi.ip.clone())).unwrap_or_default() }
}

pub fn get_users<'a>() -> Option<&'a Users> {
    unsafe {
        HOST_INFO_G.get().map(|hi| &hi.users)
    }
}

pub fn get_mac_addr() -> String {
    unsafe { HOST_INFO_G.get().and_then(|hi| Some(hi.mac.clone())).unwrap_or_default() }
}

fn get_hg_sys() -> Option<&'static mut System> {
    unsafe { HOST_INFO_G.get_mut().and_then(|hi| Some(&mut hi.sys)) }
}

pub fn get_host() -> HostDto {
    let cpudto = get_cpu();
    let disks = Disks::new_with_refreshed_list();
    let hd = HostDto {
        hostname: System::host_name().unwrap_or_default(),
        kernel_version: System::kernel_version().unwrap_or_default(),
        os_name: System::name().unwrap_or_default(),
        os_version: System::os_version().unwrap_or_default(),
        cpu_arch: System::cpu_arch().unwrap_or_default(),
        distribution: System::distribution_id(),
        os_type: OS_TYPE.to_string(),
        ip: get_ip(),
        mac: get_mac_addr(),
        memory: get_memory().total,
        cpu_count: cpudto.count,
        cpu_hz: cpudto.cpu_hz,
        disk: disks.iter().map(|d| to_round_unit(d.total_space(), SizeUnit::G)).collect(),
    };
    hd
}

pub fn get_cpu() -> CpuDto {
    let mut dto = CpuDto::default();
    get_with_sys(|sys| {
        sys.refresh_cpu();
        let cpus = sys.cpus();
        let cpu_size = cpus.len();
        let gcpu = sys.global_cpu_info();
        dto.name = cpus[0].name().to_string();
        dto.count = cpu_size;
        dto.cpu_hz = cpus[0].frequency();
    });
    dto
}

pub fn get_cpu_usage() -> f32 {
    let mut u = 0.0;
    get_with_sys(|sys| {
        sys.refresh_cpu_usage();
        u = sys.global_cpu_info().cpu_usage();
    });
    u
}

pub fn get_memory() -> MemoryDto {
    let mut dto = MemoryDto::default();
    get_with_sys(|sys| {
        sys.refresh_memory();
        dto.total = to_round_unit(sys.total_memory(), SizeUnit::G);
        dto.used = to_round_unit(sys.used_memory(), SizeUnit::M);
        dto.free = to_round_unit(sys.free_memory(), SizeUnit::M);
        dto.available = to_round_unit(sys.available_memory(), SizeUnit::M);
        dto.swap_free = to_round_unit(sys.free_swap(), SizeUnit::M);
        dto.swap_used = to_round_unit(sys.used_swap(), SizeUnit::M);
        dto.swap_total = to_round_unit(sys.total_swap(), SizeUnit::M);
    });
    dto
}

pub fn get_disk_list() -> Vec<DiskDto> {
    let mut vec_dto = Vec::new();
    get_with_sys(|sys| {
        let disks = Disks::new_with_refreshed_list();
        for disk in disks.list() {
            let st = to_round_unit(disk.total_space(), SizeUnit::G);
            let at = to_round_unit(disk.available_space(), SizeUnit::M);
            let mut dto = DiskDto::default();
            dto.label = disk.mount_point().to_str().map(|s| s.replacen(":\\", ":", 1).to_string()).unwrap_or_default();
            dto.name = disk.name().to_str().map(|s| s.to_string()).unwrap_or_default();
            dto.kind = disk.kind().to_string();
            dto.total = st;
            dto.available = at;
            vec_dto.push(dto);
        }
    });
    vec_dto
}

pub fn get_nets_list() -> Vec<NetDto> {
    use netstat::*;
    let mut vec_dto = Vec::new();
    get_with_sys(|sys| {
        let sockets_info = get_sockets_info(AddressFamilyFlags::IPV4, ProtocolFlags::TCP).unwrap();
        for si in sockets_info {
            let mut dto = NetDto::default();
            if si.associated_pids.len() > 0 {
                dto.pid = si.associated_pids[0]
            }
            match si.protocol_socket_info {
                ProtocolSocketInfo::Tcp(tcp_si) => {
                    dto.port = tcp_si.local_port;
                    dto.addr = format!("{}", tcp_si.local_addr);
                    dto.v4 = true;
                    dto.protocol = "TCP".to_string();
                }
                ProtocolSocketInfo::Udp(udp_si) => {
                    dto.port = udp_si.local_port;
                    dto.addr = format!("{}", udp_si.local_addr);
                    dto.v4 = false;
                    dto.protocol = "UDP".to_string();
                }
            }
            vec_dto.push(dto);
        }
    });
    vec_dto
}

pub fn get_process_list() -> Vec<ProcessDto> {
    sysinfo::set_open_files_limit(2);
    let mut vec_dto = Vec::new();
    get_with_sys(|sys| {
        let users = Users::new_with_refreshed_list();
        sys.refresh_processes_specifics(
            ProcessRefreshKind::new()
                .with_memory()
                .with_cpu()
                .with_exe(UpdateKind::OnlyIfNotSet)
        );
        sys.processes().iter().for_each(|(pid, p)| {
            let mut dto = ProcessDto::default();
            dto.pid = pid.as_u32();
            dto.ppid = p.parent().map_or(0, |p| p.as_u32());
            dto.cmd = p.cmd().join(" ");
            dto.pwd = p.cwd().map(|pth| pth.to_str().unwrap_or_default().to_string()).unwrap_or_default();
            dto.exe = p.exe().map(|pth| pth.to_str().unwrap_or_default().to_string()).unwrap_or_default();
            dto.usage = p.cpu_usage();
            dto.memory = to_round_unit(p.memory(), SizeUnit::K);
            dto.run_time = p.run_time();
            dto.start_time = p.start_time();
            if let Some(uid) = p.user_id() {
                dto.user = get_users().map(|us| us.get_user_by_id(uid)
                    .map(|u| u.name().to_string()).unwrap_or_default())
                    .unwrap_or_default();
                dto.uid = uid.to_string();
            }
            if dto.exe == "" && dto.cmd == "" {} else {
                vec_dto.push(dto);
            }
        });
    });
    vec_dto
}

pub fn get_user_list() -> Vec<UserDto> {
    let mut vec_dto = Vec::new();
    get_with_sys(|sys| {
        let us = Users::new_with_refreshed_list();
        us.list().iter().for_each(|u| {
            let dto = UserDto {
                id: u.id().to_string(),
                name: u.name().to_string(),
                groups: u.groups().iter().map(|g| g.name().to_string()).collect::<Vec<String>>().join(","),
            };
            vec_dto.push(dto);
        })
    });
    vec_dto
}

pub fn get_group_list() -> Vec<GroupDto> {
    let mut vec_dto = Vec::new();
    get_with_sys(|sys| {
        let gs = Groups::new_with_refreshed_list();
        gs.iter().for_each(|g| {
            let dto = GroupDto {
                id: g.id().to_string(),
                name: g.name().to_string(),
            };
            vec_dto.push(dto);
        })
    });
    vec_dto
}

fn get_with_sys<F>(f: F)
    where
        F: FnOnce(&mut System)
{
    unsafe {
        if let Some(sys) = HOST_INFO_G.get_mut().and_then(|hi| Some(&mut hi.sys)) {
            f(sys)
        }
    }
}

/// byte转换为M,G单位
fn to_round_unit(n: u64, size_unit: SizeUnit) -> u32 {
    match size_unit {
        SizeUnit::G => {
            (n as f64 / 1024.0 / 1024.0 / 1024.0).round() as u32
        }
        SizeUnit::M => {
            (n as f64 / 1024.0 / 1024.0).round() as u32
        }
        SizeUnit::K => {
            (n as f64 / 1024.0).round() as u32
        }
    }
}

#[cfg(test)]
#[test]
fn test_host_dto() {
    init();
    let hd = get_host();
    println!("{:?}", hd);
    print_test(get_process_list());
    print_test(get_nets_list());
    print_test(get_user_list());
    print_test(get_disk_list());
    print_test(get_group_list());
}

fn print_test<T: Debug>(vec: Vec<T>) {
    for x in vec {
        println!("{:?}", x);
    }
}